/*
 * main.cpp
 *
 *  Created on: 17.01.2012
 *      Author: knz
 */

#include <iostream>
#include <string>
#include <sstream>
#include <map>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <cerrno>
#include <execinfo.h>

#include "application.h"

void print_usage()
{
    std::cout << "Usage: " << g_zAppName.data() << " options"                                          << std::endl;
    std::cout << "    -h  --help                  Display this usage"                              << std::endl;
    std::cout << "    -d  --daemon                Daemon mode"                                     << std::endl;
    std::cout << "    -p  --port <num>            Explicit point of port number (3000 by default)" << std::endl;
    std::cout << "    -a  --address <ip-address>  Explicit point of IP-address in dot-notation"    << std::endl;
    std::cout << "    -v  --verbose <level>       Verbose mode"                                    << std::endl;
    std::cout << "      verbose levels:"                                                           << std::endl;
    std::cout << "        0 (TL_EMERG)   system is unusable"                                       << std::endl;
    std::cout << "        1 (TL_ALERT)   action must be taken immediately"                         << std::endl;
    std::cout << "        2 (TL_CRIT)    critical conditions"                                      << std::endl;
    std::cout << "        3 (TL_ERR)     error conditions (default)"                               << std::endl;
    std::cout << "        4 (TL_WARNING) warning conditions"                                       << std::endl;
    std::cout << "        5 (TL_NOTICE)  normal, but significant, condition"                       << std::endl;
    std::cout << "        6 (TL_INFO)    informational message"                                    << std::endl;
    std::cout << "        7 (TL_DEBUG)   debug-level message"                                      << std::endl;
    std::cout << "    -e  --stderr                Redirect the log messages to stderr"             << std::endl;
}

bool cmd_line_parser(int argc, char *argv[])
{
    const char* const short_options = "hv:edp:a:";
    const struct option long_options[] = {
        {"help",             0, 0, 'h'},
        {"verbose",          1, 0, 'v'},
        {"stderr",           0, 0, 'e'},
        {"daemon",           0, 0, 'd'},
        {"port",             1, 0, 'p'},
        {"address",          1, 0, 'a'},
        {0, 0, 0, 0}              // терминатор массива
    };

    int next_option;

    do {
        next_option = getopt_long(argc, argv, short_options, long_options, NULL);

        switch (next_option) {
        case 'h':
            print_usage();
            return false;
            break;
        case 'v':
            {
                std::stringstream ss(optarg);
                ss >> g_iVerboseLevel;
                if (!ss.eof()) {
                    std::cerr << "Error: Verbose level value is not integer value!" << std::endl;
                    print_usage();
                    return false;
                }
            }
            break;
        case 'e':
            g_bStderrMode = true;
            break;
        case 'd':
            g_bDaemonMode = true;
            break;
        case 'p':
            {
                std::stringstream ss(optarg);
                ss >> g_iPort;
                if (!ss.eof()) {
                    std::cerr << "Error: Port value is not integer value!" << std::endl;
                    print_usage();
                    return false;
                }
            }
            break;
        case 'a':
            {
                int len = strlen(optarg);
                if (len == 0) {
                    print_usage();
                    return false;
                }
                g_sIpAddress.clear();
                g_sIpAddress.append(optarg);
            }
            break;
        case '?':
            print_usage();
            return false;
            break;
        }
    } while (next_option != -1);

    return true;
}

//! \brief Получение имени файла приложения
void fixAppName(const char *argv0)
{
    char *argv0_copy = new char[strlen(argv0) + 1];
    strcpy(argv0_copy, argv0);
    {
        g_zAppName.clear();
        g_zAppName.append(basename(argv0_copy));
    }
    delete [] argv0_copy;
}

//! \brief Получение абсолютного пути к имени файла приложения независимо от каталога запуска
/*!
  \return Возвращает true если получен абсолютный путь, иначе, если путь относительный - false
 */
bool fixAbsoluteAppPath(const char *argv0)
{
    char *argv0_copy = new char[strlen(argv0) +1];
    strcpy(argv0_copy, argv0);

    bool bAbsolutePath = true;

    g_zAppPath.clear();
    if (argv0_copy[0] == '.') {
        // если путь к приложению начинается с точки (относительный путь)
        char *buf = 0;
        // первоначальный размер буфера
        int buf_size = 64;
        // флаг успешного получения абсолютного пути
        bool ok = false;
        while (1) {
            delete [] buf;
            buf = new char[buf_size];
            getcwd(buf, buf_size);
            if (buf == 0 || strlen(buf) == 0) {
                // при неудачном завершении функции проверим ошибку недостаточного размера буфера
                if (errno == ERANGE) {
                    buf_size *= 2;
                    continue;
                }
            } else {
                ok = true;
            }
            break;
        }
        if (ok) {
            g_zAppPath.append(buf);
            g_zAppPath.append(dirname(argv0_copy)+1);
        } else {
            g_zAppPath.append(dirname(argv0_copy));
            bAbsolutePath = false;
        }
        delete [] buf;
    } else {
        g_zAppPath.append(dirname(argv0_copy));
    }

    if (!g_zAppPath.isRight("/")) g_zAppPath.append('/');

    delete [] argv0_copy;
    return bAbsolutePath;
}

std::map<int, std::string> map_signal;

void signal_handler(int signal_number)
{
    IF_NOTICE_LEVEL
    LV_NOTICE("System signal %s (number %d) is received",
              map_signal[signal_number].c_str(),
              signal_number);

    if (signal_number == SIGSEGV) {
        // SEGV — англ. segmentation violation — нарушение сегментации.
        LG_ERR("Application done due to SIGSEGV");

        // нагло сперто с http://tdistler.com/2008/11/15/how-to-print-a-stack-backtrace-programatically-in-linux
        void* tracePtrs[100];
        int count = backtrace(tracePtrs, 100);
        char** funcNames = backtrace_symbols(tracePtrs, count);
        LG_ERR("backtrace:");
        for (int i = 0; i < count; i++) {
            LV_ERR("%s", funcNames[i]);
        }
        free(funcNames);

        g_saStopFlag = 1;
//        Application::deleteApplication();
        exit(1); // Обязательно, иначе будет зацикливание при повторе инструкции
    }

    if (signal_number == SIGINT) {
        // INT — сокращенное написание англ. interrupt — прервать.
        LG_ERR("Application done due to SIGINT");
        g_saStopFlag = 1;
//        Application::deleteApplication();
        exit(1); // Обязательно, иначе будет зацикливание при повторе инструкции
    }

    if (g_saSleepServerFlag) {
        IF_NOTICE_LEVEL
        LG_NOTICE("Server done by system signal at the time of wait sleep");
    }

    // По этому флагу завершится основной цикл приложения
    g_saStopFlag = 1;
}

int main(int argc, char* argv[])
{
    // Определим имя файла запущенного приложения
    fixAppName(argv[0]);
    // Определим путь к имени файла запущенного приложения
    fixAbsoluteAppPath(argv[0]);

    // Разберем аргументы командной строки
    if (!cmd_line_parser(argc, argv)) {
        return 1;
    }

    if (g_bDaemonMode) {
    	// moving into daemon mode
    	pid_t pid, sid;
    	unsigned int fd;
    	struct rlimit flim;
    	if( getpid() != 1 )
    	{
    		signal(SIGTTOU, SIG_IGN);
    		signal(SIGTTIN, SIG_IGN);
    		signal(SIGTSTP, SIG_IGN);
    	}
    	pid = fork();
    	if (pid < 0) {
    		std::cerr << "Can't fork for daemon mode" << std::endl;
    		exit(-1);
    	}
    	if (pid > 0) {
    		exit(0);
    	}
    	umask(0);
    	sid = setsid();
    	if (sid < 0) {
    		exit(-1);
    	}
    	getrlimit(RLIMIT_NOFILE, &flim);
    	for ( fd = 0; fd < flim.rlim_max; fd++ )
    		close( fd );

    	/*
    	 * http://www.citforum.ru/programming/unix/daemons/
    	 * Во время работы демона дескрипторы стандартных потоков ввода, вывода и ошибок
    	 * также должны быть открыты, поскольку они необходимы многим функциям стандартной
    	 * библиотеки. В то же время, эти дескприторы не должны указывать на какие-либо
    	 * реальные потоки ввода/вывода. Для того, чтобы решить эту задачу, мы закрываем
    	 * первые три дескриптора, а затем снова открываем их, указывая в качестве
    	 * имени файла /dev/null:
    	 */
    	int stdioFD = open("/dev/null", O_RDWR);
    	dup(stdioFD);
    	dup(stdioFD);
    }

    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = &signal_handler;
    sigaction(SIGINT,  &sa, NULL); // INT  — interrupt — прервать (останов с терминала: Ctrl+C)
    sigaction(SIGQUIT, &sa, NULL); // QUIT - выйти (останов с терминала)
    sigaction(SIGTERM, &sa, NULL); // TERM - terminate — завершить.
    sigaction(SIGSEGV, &sa, NULL); // SEGV - segmentation violation — нарушение сегментации.
    map_signal[SIGINT]  = "SIGINT";
    map_signal[SIGQUIT] = "SIGQUIT";
    map_signal[SIGTERM] = "SIGTERM";
    map_signal[SIGSEGV] = "SIGSEGV";

    g_saStopFlag  = 0;
    g_saSleepServerFlag = 0;
    Application::createApplication();

    // инициализация сервера
    if (!g_pApp->serverInit(g_sIpAddress.c_str(), g_iPort)) {
        LG_ERR("Can't start server");
        Application::deleteApplication();
        return -1;
    }

    // !!! снять комментарий с "g_saStopFlag = 1" для отладки завершения приложения
    //g_saStopFlag = 1;

    int res = 0;
    try {
        // запуск цикла приложения
        if (g_pApp->exec()) {
            IF_NOTICE_LEVEL
            LG_NOTICE("Server normally done");
        } else {
            res =  -2;
            IF_ERR_LEVEL
            LG_ERR("Server done due error");
        }
    } catch (int) {
    }

    Application::deleteApplication();
    return res;
}


